﻿using System;
using System.Collections.Specialized;
using System.IO;
using DBFile.Core.Configuration;
using DBFile.Core.Interfaces;
using DBFile.Core.Web;
using DBFile.Core.Extensions;
using Microsoft.Win32;
using DBFile.Core.Impl;

namespace DBFile.Core
{
	/// <summary>
	/// This class is the sort of Business logic to the file handling (save, open, delete, list)
	/// </summary>
	public static class DBFile
	{

		private static bool intialized = false;
		private static object padLock = new object();
		private static DBFileProvider provider;
		private static DBFileProviderCollection providers;

		#region Public Methods
		/// <summary>
		/// Takes in a filename, text to save as the file and the contenttype to make the file.  This
		/// method was writen to solve the problem of creating an XML file on the fly.
		/// </summary>
		/// <param name="fileName">name to save the file as</param>
		/// <param name="fileText">text body of the file</param>
		/// <param name="contentType">MIME type to save the text file as</param>
		/// <param name="overwrite">flag to indicate whether or not to overwrite an existing version of the file</param>
		/// <returns>rows affected</returns>
		public static int Save(string filename, string fileText, string contentType, bool overwrite)
		{
			// get the bytes of the text
			System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
			byte[] buffer = encoding.GetBytes(fileText);
			int contentLength = buffer.Length;

			return Save(filename, buffer, contentType, contentLength, overwrite);
		}
		/// <summary>
		/// Takes in a filename, text to save as the file and the contenttype to make the file.  This
		/// method was writen to solve the problem of creating an XML file on the fly.
		/// </summary>
		/// <param name="fileName">name to save the file as</param>
		/// <param name="fileText">text body of the file</param>
		/// <param name="contentType">MIME type to save the text file as</param>
		/// <returns>rows affected</returns>
		public static int Save(string filename, string fileText, string contentType)
		{
			return Save(filename, fileText, contentType, false);
		}

		/// <summary>
		/// Takes in a filename, stream and the contenttype to save an file into the database.
		/// </summary>
		/// <param name="filename">name of the file</param>
		/// <param name="inputStream">Stream object to save as the file</param>
		/// <param name="contentType">MIME type of the file</param>
		/// <param name="overwrite">flag to indicate whether or not to overwrite an existing version of the file</param>
		/// <returns>rows affected</returns>
		public static int Save(string filename, System.IO.Stream inputStream, string contentType, bool overwrite)
		{
			int contentLength = 0;
			byte[] buffer = null;

			if (inputStream != null)
			{
				// get the length of the stream
				contentLength = (int)inputStream.Length;

				// move to the begining of the stream to get all bytes
				inputStream.Seek(0, System.IO.SeekOrigin.Begin);
				buffer = new byte[contentLength];

				// write bytes to buffer
				inputStream.Read(buffer, 0, contentLength);
			}
			else
			{
				buffer = new byte[0];
			}
			return Save(filename, buffer, contentType, contentLength, overwrite);
		}

		/// <summary>
		/// Takes in a filename, stream and the contenttype to save an file into the database.
		/// </summary>
		/// <param name="filename">name of the file</param>
		/// <param name="inputStream">Stream object to save as the file</param>
		/// <param name="contentType">MIME type of the file</param>
		/// <returns>rows affected</returns>
		public static int Save(string filename, System.IO.Stream inputStream, string contentType)
		{
			return Save(filename, inputStream, contentType, false);
		}

		/// <summary>
		/// Takes in filename and stream.  The contenttype is derived from the file extension.
		/// </summary>
		/// <param name="filename">The filename.</param>
		/// <param name="inputStream">The input stream.</param>
		/// <returns></returns>
		public static int Save(string filename, System.IO.Stream inputStream)
		{
			return Save(filename, inputStream, GetContentType(Path.GetExtension(filename)));
		}

		/// <summary>
		/// Takes in contentlength, filename, conenttype and a stream to save into the database.  This method
		/// is the more useful out of the two because it can be used outside of just posting a file to a web
		/// page.
		/// </summary>
		/// <param name="fileName">name of the file</param>
		/// <param name="streamBytes">stream object to save into database</param>
		/// <param name="contentType">MIME type of the stream content</param>
		/// <param name="contentLength">length of the byte array in the stream</param>
		/// <param name="overwrite">flag to indicate whether or not to overwrite an existing version of the file</param>
		/// <returns>number of rows affected by the insert (0 or 1)</returns>
		public static int Save(string filename, byte[] streamBytes, string contentType, long contentLength, bool overwrite)
		{
			if (filename.Length == 0)
			{
				return 0;
			}

            if (Provider.UseCompression)
            {
                return Provider.Insert(filename, streamBytes.Compress(filename), contentType, contentLength, overwrite);
            }
            else
            {
                return Provider.Insert(filename, streamBytes, contentType, contentLength, overwrite);
            }
		}
		/// <summary>
		/// Takes in contentlength, filename, conenttype and a stream to save into the database.  This method
		/// is the more useful out of the two because it can be used outside of just posting a file to a web
		/// page.
		/// </summary>
		/// <param name="fileName">name of the file</param>
		/// <param name="streamBytes">stream object to save into database</param>
		/// <param name="contentType">MIME type of the stream content</param>
		/// <param name="contentLength">length of the byte array in the stream</param>
		/// <returns>number of rows affected by the insert (0 or 1)</returns>
		public static int Save(string filename, byte[] streamBytes, string contentType, long contentLength)
		{
			return Save(filename, streamBytes, contentType, contentLength, false);
		}
		/// <summary>
		/// Takes the HttpPostedFile and passes it off to insert into the database.
		/// </summary>
		/// <param name="file">HttpPostedFile object from the Request</param>
		/// <param name="overwrite">flag to indicate whether or not to overwrite an existing version of the file</param>
		/// <returns>Number of rows affected by the insert (0 or 1)</returns>
		public static int Save(System.Web.HttpPostedFile file, bool overwrite)
		{
			// first check if the file is null, if so exit
			if (file == null || file.ContentLength == 0)
			{
				return 0;
			}

			// get items off the postedfile
			int contentLength = file.ContentLength;
			System.IO.Stream inputStream = file.InputStream;
			string contentType = file.ContentType;

			FileInfo fi = new FileInfo(file.FileName);
			string fileName = fi.Name; ;

			// read in the stream to get the bytes
			byte[] buffer = new byte[contentLength];
			int byteCount = inputStream.Read(buffer, 0, contentLength);

			// return the number of rows affected - 1 or 0
			return Save(fileName, buffer, contentType, contentLength, overwrite);
		}
		/// <summary>
		/// Takes the HttpPostedFile and passes it off to insert into the database.
		/// </summary>
		/// <param name="file">HttpPostedFile object from the Request</param>
		/// <returns>Number of rows affected by the insert (0 or 1)</returns>
		public static int Save(System.Web.HttpPostedFile file)
		{
			return Save(file, false);
		}

        public static int Save(System.Web.HttpPostedFileBase file, bool overwrite)
        {
            return Save(file, null, overwrite);
        }

        public static int Save(System.Web.HttpPostedFileBase file, string fileName, bool overwrite)
        {
            // first check if the file is null, if so exit
            if (file == null || file.ContentLength == 0)
            {
                return 0;
            }

            // get items off the postedfile
            int contentLength = file.ContentLength;
            System.IO.Stream inputStream = file.InputStream;
            string contentType = file.ContentType;

            if (string.IsNullOrEmpty(fileName))
            {
                FileInfo fi = new FileInfo(file.FileName);
                fileName = fi.Name;
            }

            // read in the stream to get the bytes
            byte[] buffer = new byte[contentLength];
            int byteCount = inputStream.Read(buffer, 0, contentLength);

            // return the number of rows affected - 1 or 0
            return Save(fileName, buffer, contentType, contentLength, overwrite);
        }

        public static int Save(System.Web.HttpPostedFileBase file)
        {
            return Save(file, null, false);
        }

        public static int Save(System.Web.HttpPostedFileBase file, string fileName)
        {
            return Save(file, fileName, false);
        }

		/// <summary>
		/// Takes a filepath to check the database for the listed file, if so the file stream is returned.
		/// </summary>
		/// <param name="filename">path or filename to look for</param>
		/// <param name="response">Response object to write the file to</param>
		/// <param name="isAttachment">Indicates whether to add the attachment header to the Response</param>
		public static void Open(string filename, System.Web.HttpResponse response, bool isAttachment)
		{
			// call open method to get an HttpPostedFile object
			HttpPostedFile file = OpenFile(filename);

			// if no bytes were returned leave
			if (file == null || file.ContentLength == 0)
				return;

			// set content type
			response.ContentType = file.ContentType;

			// if it is an attachment, add the header
			if (isAttachment)
			{
				response.AppendHeader("Content-Disposition", "attachment; filename=" + file.FileName);
			}
			// write the bytes out to the stream
			// NOTE: could optimize to not require the whole byte array read in at once if needing to deal with
			// large files.
			byte[] buffer = new byte[file.ByteCount];
			file.Read(buffer, 0, file.ByteCount);
			response.BinaryWrite(buffer);

			file.Dispose();

		}

		/// <summary>
		/// Takes in a filename to check the database for the listed file and returns a Stream
		/// </summary>
		/// <param name="filename">name of the file to look for</param>
		/// <returns>Stream object of the file or empty</returns>
		public static Stream Open(string filename)
		{
			// call open method to get an HttpPostedFile object
			HttpPostedFile file = OpenFile(filename);

			// return the file as a Stream object
			return file.InputStream;

		}
		/// <summary>
		/// Takes in a filename to check the database for the listed file and return a StreamReader.
		/// This method is designed to be used for UTF8 encoded text files.
		/// </summary>
		/// <param name="filename">name of the file to return</param>
		/// <returns>UTF8 encoded StreamReader object</returns>
		public static StreamReader OpenText(string filename)
		{
			// call open method to get an HttpPostedFile object
			HttpPostedFile file = OpenFile(filename);
            
			// return the file encoded in a streamreader
			return new StreamReader(file.InputStream, System.Text.Encoding.UTF8);
		}
		/// <summary>
		/// Gets a ListDictionary of all files saved in the system
		/// </summary>
		/// <param name="appPath">Application path to append to front of url for hyperlinks</param>
		/// <returns>List of file urls and filenames</returns>
		public static NameValueCollection GetFileList(string appPath)
		{
            NameValueCollection names = new NameValueCollection();

            foreach (string name in Provider.GetFileNameList())
            {
                names.Add(appPath + name, name);
            }

            return names;
		}

        public static string[] GetFileNameList()
        {
            return Provider.GetFileNameList();
        }
        

		/// <summary>
		/// Deletes a file, if it exists
		/// </summary>
		/// <param name="filename">Name of the file to delete</param>
		/// <returns>number of rows affected</returns>
		public static int Delete(string filename)
		{
			if (filename.Length == 0)
			{
				return 0;
			}

			return Provider.Delete(filename);
		}

		/// <summary>
		/// Checks to see if the file exists or not
		/// </summary>
		/// <param name="filename">name of the file</param>
		/// <returns>true if the file exists, false othewise</returns>
		public static bool Exists(string filename)
		{
			if (filename.Length == 0)
			{
				return false;
			}

			return Provider.Exists(filename);
		}
		/// <summary>
		/// Copies a file (already stored in the database) over to another filename
		/// </summary>
		/// <param name="sourceFileName">name of the file to copy over</param>
		/// <param name="destinationFileName">name to copy the file over to</param>
		/// <param name="overwrite">indicates whether to overwrite an existing file or not</param>
		/// <returns>number of rows affected</returns>
		public static int Copy(string sourceFileName, string destinationFileName, bool overwrite)
		{
			if (sourceFileName.Length == 0 || destinationFileName.Length == 0)
			{
				return 0;
			}

			return Provider.Copy(sourceFileName, destinationFileName, overwrite);
		}
		/// <summary>
		/// Copies a file (already stored in the database) over to another filename
		/// </summary>
		/// <param name="sourceFileName">name of the file to copy over</param>
		/// <param name="destinationFileName">name to copy the file over to</param>
		/// <returns>number of rows affected</returns>
		public static int Copy(string sourceFileName, string destinationFileName)
		{
			return Copy(sourceFileName, destinationFileName, false);
		}

		/// <summary>
		/// Moves the specified source filename.
		/// </summary>
		/// <param name="sourceFilename">The source filename.</param>
		/// <param name="destinationFilename">The destination filename.</param>
		/// <returns></returns>
		public static int Move(string sourceFilename, string destinationFilename)
		{
			int ret = 0;
            if (File.Exists(Path.Combine(Path.GetDirectoryName(destinationFilename),sourceFilename)))
			{
				ret = MoveToDb(sourceFilename, destinationFilename);
			}
			else
			{
				ret = MoveFromDb(sourceFilename, destinationFilename);
			}

			return ret;

		}
		/// <summary>
		/// Moves a file from the filesystem to the database
		/// </summary>
		/// <param name="sourceFilename">path to move the file you want to the database</param>
		/// <returns>number of inserts made</returns>
		public static int Move(string sourceFilename)
		{
			FileInfo fi = new FileInfo(sourceFilename);
			string filename = fi.Name;

            return MoveToDb(sourceFilename, filename);
		}
		#endregion

		#region Utility Methods

        private static T Open<T>(string filename) where T : IDBFile, new()
        {
            // check to see if the filename is worth going on for 
            if (filename.Length == 0)
            {
                return default(T); // return an empty
            }

            if (Provider.UseCompression)
            {
                return (T)Provider.Get<T>(filename).Decompress();
            }
            else
            {
                return Provider.Get<T>(filename);
            }
        }

		/// <summary>
		/// Private utility method used for getting a file from the database
		/// </summary>
		/// <param name="filename">name of the file to look for</param>
		/// <returns>HttpPostedFile object which will be empty if nothing was found</returns>
		internal static HttpPostedFile OpenFile(string filename)
		{
			// check to see if the filename is worth going on for 
			if (filename.Length == 0)
			{
				return new HttpPostedFile(); // return an empty
			}

			// get the file from the database
			HttpPostedFile file = Provider.Get<HttpPostedFile>(filename);

            if (Provider.UseCompression)
            {
                return new HttpPostedFile(file.Decompress());
            }
            else
            {
                return file;
            }
		}

		/// <summary>
		/// Private utility method used to get the contentType (mime type) from the registry
		/// </summary>
		/// <param name="fileExt">file extension to look up in registry</param>
		/// <returns>mime type</returns>
		public static string GetContentType(string fileExt)
		{
			const string CONTENT_TYPE = "Content Type";
			const string DEFAULT_TYPE = "text/plain";
			string contentType = DEFAULT_TYPE;

            RegistryKey rk = null;
            RegistryKey extKey = null;
            try
            {
                rk = Registry.ClassesRoot;
                extKey = rk.OpenSubKey(fileExt);

                if (extKey != null 
                    && extKey.GetValue(CONTENT_TYPE) != null)
                {
                    contentType = extKey.GetValue(CONTENT_TYPE).ToString();
                }
            }
            finally
            {
                if (extKey != null)
                {
                    extKey.Close();
                }
                if (rk != null)
                {
                    rk.Close();
                }
            }
			return contentType;
		}
		/// <summary>
		/// Opens a file from the database and saves it to the file system
		/// </summary>
		/// <param name="sourceFilename">name of file to get from database</param>
		/// <param name="destinationFilename">The destination filename.</param>
		/// <returns></returns>
		private static int MoveFromDb(string sourceFilename, string destinationFilename)
		{
            FileStream fs = new FileStream(destinationFilename, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
			MemoryStream ms = (MemoryStream)Open(sourceFilename);

			ms.WriteTo(fs);
			ms.Flush();
			ms.Close();

            fs.Flush();
			fs.Close();

			return 1;
		}
		/// <summary>
		/// Moves a file from the filesystem to the database.
		/// </summary>
		/// <param name="sourceFilename">path to the file you want to add to the database</param>
		/// <param name="destinationFilename">name to give the file in the database</param>
		/// <returns>number of inserts made</returns>
		private static int MoveToDb(string sourceFilename, string destinationFilename)
		{
			// open file and get a stream to save
			FileStream fs = new FileStream(sourceFilename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
			FileInfo fi = new FileInfo(destinationFilename); // may want to change this to the sourceFilename
			string contentType = GetContentType(fi.Extension);
			int rowsAffected = Save(destinationFilename, fs, contentType);

			fs.Close();

			return rowsAffected;
		}

		/// <summary>
		/// Gets the provider.
		/// </summary>
		/// <value>The provider.</value>
		private static DBFileProvider Provider
		{
			get
			{
				Initialize();
				return provider;
			}
		}

		#endregion

		#region Private Methods

		private static void Initialize()
		{
			lock (padLock)
			{
				if (!intialized)
				{
					try
					{
						providers = DBFileProvider.Providers;
						provider = DBFileProvider.Instance();
					}
					catch (Exception ex)
					{
						throw ex;
					}
					intialized = true;
				}
			}
		}

		#endregion Private Methods
	}
}
